#include "anry-stock-library-config.h"
#include "urlencode.h"
#include "HTTPWebRequest.h"

using namespace std;

HTTPWebRequest::HTTPWebRequest( const char* addr, const unsigned short port, const char* path, const char* method )
{
	// set member vars
	SetAddr( addr );
	SetPort( port );
	SetPath( path );
	SetMethod( method );
	
	// clear parameters
	ClearParameters();
	
	// clear headers
	ClearHeaders();
	
	// reset socket
	m_sock = INVALID_SOCKET;

	// reset request buffers
	m_request_str_len = 0;
	m_request_str = NULL;

	// reset response buffers
	m_response_str = "";
}

HTTPWebRequest::~HTTPWebRequest()
{
	FreeBuffers();
}

// set member vars
int HTTPWebRequest::SetAddr( const char* addr )
{
	m_server_addr = addr;
	
	return 0;
}

int HTTPWebRequest::SetPort( const unsigned short port )
{
	m_server_port = port;
	
	return 0;
}

int HTTPWebRequest::SetPath( const char* path )
{
	if( path[0] != '/' ) m_server_path = "/";
	m_server_path += path;

	return 0;
}

// set parameters
int HTTPWebRequest::SetParameter( std::string key, std::string value )
{
	m_parameters[key] = urlencode( value );

	return 0;
}

int HTTPWebRequest::ClearParameters()
{
	m_parameters.clear();

	return 0;
}

void HTTPWebRequest::FreeBuffers()
{
	// close socket
	if( m_sock != INVALID_SOCKET && m_sock != SOCKET_ERROR ) closesocket( m_sock );
	m_sock = INVALID_SOCKET;
	
	// free request
	m_request_str_len = 0;
	if( m_request_str )
	{
		delete [] m_request_str;
		m_request_str = NULL;
	}
}

// set struct sockaddr_in from adddress+port
int HTTPWebRequest::SetSockAddrIn( struct sockaddr_in* p_addr_struct )
{
	// assume it's ip address
#ifndef _WIN32
	p_addr_struct->sin_addr.s_addr = inet_addr( m_server_addr.c_str() );
#else
	p_addr_struct->sin_addr.S_un.S_addr = inet_addr( m_server_addr.c_str() );
#endif
	p_addr_struct->sin_family = AF_INET;
	p_addr_struct->sin_port = htons( m_server_port );

	// if not ip address
#ifndef _WIN32
	if( p_addr_struct->sin_addr.s_addr == INADDR_NONE )
#else
	if( p_addr_struct->sin_addr.S_un.S_addr == INADDR_NONE )
#endif
	{
		struct hostent* pHostent = NULL;
		pHostent = gethostbyname( m_server_addr.c_str() );
		if( pHostent == NULL || *((unsigned long*)pHostent->h_addr_list[0]) == 0 )
		{
			DEBUG_OUTPUT_WITH_FUNCTION_NAME( DEBUG_LEVEL_ERROR, "unable to parse address %s:%d.", m_server_addr.c_str(), m_server_port );
			return -1;
		}
#ifndef _WIN32
		p_addr_struct->sin_addr.s_addr = *((unsigned long*)pHostent->h_addr_list[0]);
#else
		p_addr_struct->sin_addr.S_un.S_addr = *((unsigned long*)pHostent->h_addr_list[0]);
#endif
	}

	return 0;
}

int HTTPWebRequest::DoConnect()
{
	// create socket
	m_sock = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP );
	if( m_sock == SOCKET_ERROR )
	{
		DEBUG_OUTPUT_WITH_FUNCTION_NAME( DEBUG_LEVEL_ERROR, "create connect socket failed." );
		return -1;
	}

	// prepare addr
	struct sockaddr_in server_addr;
	if( SetSockAddrIn(&server_addr) != 0 )
	{
		DEBUG_OUTPUT_WITH_FUNCTION_NAME( DEBUG_LEVEL_ERROR, "SetSockAddrIn failed." );
		return -2;
	}

	// connect to server
	if( connect(m_sock, (struct sockaddr*)&server_addr, sizeof(server_addr)) )
	{
		DEBUG_OUTPUT_WITH_FUNCTION_NAME( DEBUG_LEVEL_ERROR, "connect to server failed." );
		return -3;
	}

	return 0;
}

int HTTPWebRequest::PrepareRequestStr()
{
	// content length str
	char content_length[8] = {0};

	// concate all post vars
	map<string, string>::iterator it;
	string concate_all_post_str = "";
	for( it=m_parameters.begin(); it!=m_parameters.end(); it++ )
	{
		if( concate_all_post_str != "" ) concate_all_post_str += "&";
		concate_all_post_str += it->first;
		concate_all_post_str += "=";
		concate_all_post_str += it->second;
	}
	
	// convert post str length to char array
	snprintf( content_length, sizeof(content_length) - 1, "%d", concate_all_post_str.size() );
		
	// concate all extra headers
	string concate_all_header_str = "";
	for( it=m_headers.begin(); it!=m_headers.end(); it++ )
	{
		concate_all_header_str += it->first;
		concate_all_header_str += ": ";
		concate_all_header_str += it->second;
		concate_all_header_str += "\r\n";
	}

	// calculate request str len
	m_request_str_len = strlen(HTTP_WEB_REQUEST_POST_FORMAT) - 14 +
		m_method.size() + m_server_path.size() + m_server_addr.size()*2 + strlen(content_length) +
		concate_all_header_str.size() + concate_all_post_str.size();

	// prepare request str buffer
	m_request_str = new char[m_request_str_len + 2];
	memset( m_request_str, 0, m_request_str_len + 2 );

	// prepare the request str
	snprintf( m_request_str, m_request_str_len + 1, HTTP_WEB_REQUEST_POST_FORMAT,
		m_method.c_str(), m_server_path.c_str(), m_server_addr.c_str(),
		content_length, m_server_addr.c_str(), concate_all_header_str.c_str(),
		concate_all_post_str.c_str() );

	return 0;
}

// get response
int HTTPWebRequest::Request()
{
	int ret_code = 0;
	
	// close the old socket
	FreeBuffers();

	do
	{
		// do connect
		if( DoConnect() != 0 )
		{
			DEBUG_OUTPUT_WITH_FUNCTION_NAME( DEBUG_LEVEL_ERROR, "connect failed." );
			ret_code = -1;
			break;
		}
		
		// prepare request
		if( PrepareRequestStr() != 0 )
		{
			DEBUG_OUTPUT_WITH_FUNCTION_NAME( DEBUG_LEVEL_ERROR, "prepare request str failed." );
			ret_code = -2;
			break;
		}
		
		// send request
		if( DoSend() != 0 )
		{
			DEBUG_OUTPUT_WITH_FUNCTION_NAME( DEBUG_LEVEL_ERROR, "send request failed." );
			ret_code = -3;
			break;
		}

		// recv until end
		DoRecv();
	} while( false );
	
	return ret_code;
}

std::string HTTPWebRequest::GetResponse()
{
	return m_response_str;
}

int HTTPWebRequest::DoSend()
{
	int total_bytes_send = 0;
	int bytes_send = 0;

	while( total_bytes_send < m_request_str_len )
	{
		bytes_send = send( m_sock, m_request_str+total_bytes_send, m_request_str_len-total_bytes_send, 0 );
		if( bytes_send <= 0 ) break;
		else total_bytes_send += bytes_send;
	}
	
	// check if all send
	if( m_request_str_len != total_bytes_send ) return -1;
	
	return 0;
}

int HTTPWebRequest::DoRecv()
{
	char recv_buf[2049];
	int bytes_recv = 0;

	m_response_str = "";
	while( bytes_recv > 0 )
	{
		bytes_recv = recv( m_sock, recv_buf, 2048, 0 );
		if( bytes_recv > 0 )
		{
			recv_buf[bytes_recv] = 0;
			m_response_str += recv_buf;
		}
	}
	return 0; 
}



